#!/usr/bin/env python
"""
PyLink-SR (Python Linked Software Radio System)
(c) Bob Cowdery (G3UKB) 2008

File: app_gui.py
Date: July 2008

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

The author can be reached by email at

bob@g3ukb.co.uk

========================================================================
Module Description:
The main application gui window.

"""

#-----------------------------------------------------------------------
# System imports
import logging
import sys
import config_service as config
from time import sleep

# Import GTK libraries
try:
  import pygtk
  #tell pyGTK, if possible, that we want GTKv2
  pygtk.require("2.0")
except:
  #Some distributions come with GTK2, but not pyGTK
  pass
try:
  import gtk
  import gtk.glade
  import gobject
except:
  print "You need to install pyGTK or GTKv2 ",
  print "or set your PYTHONPATH correctly."
  print "try: export PYTHONPATH=",
  print "/usr/local/lib/python2.2/site-packages/"
  sys.exit(1)

# Application imports
import utils
from defs import *

#======================================================================
# The GUI class. There will be other classes downstream.
class AppGui:
  
  #---------------------------------------------------------------------
  def __init__(self, event_service):
    # Allow threads, without this the gui will hang all python threads
    # We don't actually use any Python threads right now but its very
    # disconcerting if you do and find they block, so leave this in
    gtk.gdk.threads_init()
    # This is how we send events into the system
    self.es = event_service
    # Initialise the glade built ui
    # Load the main window
    windowname = "PyLink-SR"
    # This loads a widget tree from the XML definition in the glade file
    self.main_tree=gtk.glade.XML (config.glade_file(), windowname)
    # Get and show the main window
    w=self.main_tree.get_widget(windowname)
    w.show_all()
    
    # Holds the transient state
    self.state = {}
    
    # Wire up the top level window events
    # All events are set up in Glade, just match the event names here
    dic = { "on_PyLink-SR_destroy" : self.destroy,
            "on_PyLink-SR_delete_event" : self.delete_event,
            "on_bpower_toggled" : self.power_toggled,
            "on_bclose_clicked" : self.exit,
            # AGC
            "on_bagc_toggled" : self.agc_toggled,
            "on_bfast_toggled" : self.agc_fast,
            "on_bmedium_toggled" : self.agc_medium,
            "on_bslow_toggled" : self.agc_slow,
            # Filters
            "on_b6k_toggled" : self.filter_6k,
            "on_b2k4_toggled" : self.filter_2k4,
            "on_b2k1_toggled" : self.filter_2k1,
            "on_b1k_toggled" : self.filter_1k,
            "on_b500_toggled" : self.filter_500,
            "on_b250_toggled" : self.filter_250,
            "on_b100_toggled" : self.filter_100,
            # Modes
            "on_blsb_toggled" : self.mode_lsb,
            "on_busb_toggled" : self.mode_usb,
            "on_bcwl_toggled" : self.mode_cwl,
            "on_bcwu_toggled" : self.mode_cwu,
            "on_bam_toggled" : self.mode_am,
            # Tuning
            # There are 9 digits to tune and they each have three
            # events so it makes quite a few events
            "on_evb100mhz_enter_notify_event" : self.enter_100mhz,
            "on_evb100mhz_leave_notify_event" : self.leave_100mhz,
            "on_evb100mhz_scroll_event" : self.scroll_100mhz,
            "on_evb10mhz_enter_notify_event" : self.enter_10mhz,
            "on_evb10mhz_leave_notify_event" : self.leave_10mhz,
            "on_evb10mhz_scroll_event" : self.scroll_10mhz,
            "on_evb1mhz_enter_notify_event" : self.enter_1mhz,
            "on_evb1mhz_leave_notify_event" : self.leave_1mhz,
            "on_evb1mhz_scroll_event" : self.scroll_1mhz,
            
            "on_evb100khz_enter_notify_event" : self.enter_100khz,
            "on_evb100khz_leave_notify_event" : self.leave_100khz,
            "on_evb100khz_scroll_event" : self.scroll_100khz,
            "on_evb10khz_enter_notify_event" : self.enter_10khz,
            "on_evb10khz_leave_notify_event" : self.leave_10khz,
            "on_evb10khz_scroll_event" : self.scroll_10khz,
            "on_evb1khz_enter_notify_event" : self.enter_1khz,
            "on_evb1khz_leave_notify_event" : self.leave_1khz,
            "on_evb1khz_scroll_event" : self.scroll_1khz,
            
            "on_evb100hz_enter_notify_event" : self.enter_100hz,
            "on_evb100hz_leave_notify_event" : self.leave_100hz,
            "on_evb100hz_scroll_event" : self.scroll_100hz,
            "on_evb10hz_enter_notify_event" : self.enter_10hz,
            "on_evb10hz_leave_notify_event" : self.leave_10hz,
            "on_evb10hz_scroll_event" : self.scroll_10hz,
            "on_evb1hz_enter_notify_event" : self.enter_1hz,
            "on_evb1hz_leave_notify_event" : self.leave_1hz,
            "on_evb1hz_scroll_event" : self.scroll_1hz,           
            }
    
    # This will connect up all the events in one hit
    self.main_tree.signal_autoconnect (dic)
    
    # Now we need a reference to all the controls
    # A bit long winded but easy to follow
    # All controls in the tree must have a unique name
    self.l_100mhz = self.main_tree.get_widget('l_100mhz')
    self.l_10mhz = self.main_tree.get_widget('l_10mhz')
    self.l_1mhz = self.main_tree.get_widget('l_1mhz')   
    self.l_100khz = self.main_tree.get_widget('l_100khz')
    self.l_10khz = self.main_tree.get_widget('l_10khz')
    self.l_1khz = self.main_tree.get_widget('l_1khz')
    self.l_100hz = self.main_tree.get_widget('l_100hz')
    self.l_10hz = self.main_tree.get_widget('l_10hz')
    self.l_1hz = self.main_tree.get_widget('l_1hz')
    
    self.blsb = self.main_tree.get_widget('blsb')
    self.busb = self.main_tree.get_widget('busb')
    self.bcwl = self.main_tree.get_widget('bcwl')
    self.bcwu = self.main_tree.get_widget('bcwu')
    self.bam = self.main_tree.get_widget('bam')
    
    self.b6k = self.main_tree.get_widget('b6k')
    self.b2k4 = self.main_tree.get_widget('b2k4')
    self.b2k1 = self.main_tree.get_widget('b2k1')
    self.b1k = self.main_tree.get_widget('b1k')
    self.b500 = self.main_tree.get_widget('b500')
    self.b250 = self.main_tree.get_widget('b250')
    self.b100 = self.main_tree.get_widget('b100')
    
    self.bagc = self.main_tree.get_widget('bagc')
    self.bfast = self.main_tree.get_widget('bfast')
    self.bmedium = self.main_tree.get_widget('bmedium')
    self.bslow = self.main_tree.get_widget('bslow')
    
    self.mainbox = self.main_tree.get_widget('vbox2')
    self.mainbox.set_sensitive(False)

    # Init the current freq
    self.freq = 0.0
    
    # Avoid circular events
    # One issue with messaging is that a lot of controls generate an event when they
    # are changed programmatically. So you hit a button, it sends a message, then a 
    # message comes back that causes the button state to change which causes it to 
    # generate an event ... 
    self.ignore = False
    
  #=====================================================================
  # Return the transient state
  def get_state(self):
    return self.state
  
  #---------------------------------------------------------------------
  # GUI generated events (i.e. things that happen when you play with widgets)
  
  #=====================================================================
  # Events for the main window
  def delete_event(self, widget, event, data=None):
    # If you return FALSE in the "delete_event" signal handler,
    # GTK will emit the "destroy" signal. Returning TRUE means
    # you don't want the window to be destroyed.
    # This is useful for popping up 'are you sure you want to quit?'
    # type dialogs.

    # Change FALSE to TRUE and the main window will not be destroyed
    # with a "delete_event".
    return False
  
  #---------------------------------------------------------------------
  def destroy(self, widget, data=None):
    self.es.publish('ui_hints', ('terminate', None))
    sleep(1)
    gtk.main_quit()
  
  #---------------------------------------------------------------------
  def exit(self, widget):
    self.es.publish('ui_hints', ('terminate', None))
    sleep(1)
    gtk.main_quit()
    
  #---------------------------------------------------------------------
  def power_toggled(self, widget):
    if widget.get_active():
      self.es.publish('dsp_events', ('start_dsp', None))
      self.es.publish('hw_events', ('start_hw',None))
      self.mainbox.set_sensitive(True)
    else:
      self.es.publish('dsp_events', ('stop_dsp', None))
      self.es.publish('hw_events', ('stop_hw',None))
      self.mainbox.set_sensitive(False)

  #=====================================================================
  # Events for the AGC controls
  #---------------------------------------------------------------------
  def agc_toggled(self, widget):
    if not self.ignore:
      if widget.get_active():
        state = True
      else:
        state = False
      self.es.publish('dsp_events', ('set_agc', state))
   
  #---------------------------------------------------------------------
  def agc_fast(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_agc_rate', 'AGC_FAST'))
     
  #---------------------------------------------------------------------
  def agc_medium(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_agc_rate', 'AGC_MED'))
  
  #---------------------------------------------------------------------
  def agc_slow(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_agc_rate', 'AGC_SLOW'))
 
  #=====================================================================
  # Events for each of the 9 frequency digits
  # Mouse enter, mouse leave and wheel scroll
  #---------------------------------------------------------------------
  def enter_100mhz(self, widget, x):
    l = self.l_100mhz.get_label()
    l = l.replace('24', '26')
    self.l_100mhz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_100mhz(self, widget, x):
    l = self.l_100mhz.get_label()
    l = l.replace('26', '24')
    l = self.l_100mhz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_100mhz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(100.0)
    else:
      self.freq_inc(-100.0)
    
  #---------------------------------------------------------------------
  def enter_10mhz(self, widget, x):
    l = self.l_10mhz.get_label()
    l = l.replace('24', '26')
    self.l_10mhz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_10mhz(self, widget, x):
    l = self.l_10mhz.get_label()
    l = l.replace('26', '24')
    l = self.l_10mhz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_10mhz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(10.0)
    else:
      self.freq_inc(-10.0)
    
  #---------------------------------------------------------------------
  def enter_1mhz(self, widget, x):
    l = self.l_1mhz.get_label()
    l = l.replace('24', '26')
    self.l_1mhz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_1mhz(self, widget, x):
    l = self.l_1mhz.get_label()
    l = l.replace('26', '24')
    l = self.l_1mhz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_1mhz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(1.0)
    else:
      self.freq_inc(-1.0)
    
  #---------------------------------------------------------------------
  def enter_100khz(self, widget, x):
    l = self.l_100khz.get_label()
    l = l.replace('20', '24')
    self.l_100khz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_100khz(self, widget, x):
    l = self.l_100khz.get_label()
    l = l.replace('24', '20')
    l = self.l_100khz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_100khz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(0.1)
    else:
      self.freq_inc(-0.1)
    
  #---------------------------------------------------------------------
  def enter_10khz(self, widget, x):
    l = self.l_10khz.get_label()
    l = l.replace('20', '24')
    self.l_10khz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_10khz(self, widget, x):
    l = self.l_10khz.get_label()
    l = l.replace('24', '20')
    l = self.l_10khz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_10khz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(0.01)
    else:
      self.freq_inc(-0.01)
    
  #---------------------------------------------------------------------
  def enter_1khz(self, widget, x):
    l = self.l_1khz.get_label()
    l = l.replace('20', '24')
    self.l_1khz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_1khz(self, widget, x):
    l = self.l_1khz.get_label()
    l = l.replace('24', '20')
    l = self.l_1khz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_1khz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(0.001)
    else:
      self.freq_inc(-0.001)

  #---------------------------------------------------------------------
  def enter_100hz(self, widget, x):
    l = self.l_100hz.get_label()
    l = l.replace('16', '20')
    self.l_100hz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_100hz(self, widget, x):
    l = self.l_100hz.get_label()
    l = l.replace('20', '16')
    l = self.l_100hz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_100hz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(0.0001)
    else:
      self.freq_inc(-0.0001)
    
  #---------------------------------------------------------------------
  def enter_10hz(self, widget, x):
    l = self.l_10hz.get_label()
    l = l.replace('16', '20')
    self.l_10hz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_10hz(self, widget, x):
    l = self.l_10hz.get_label()
    l = l.replace('20', '16')
    l = self.l_10hz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_10hz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(0.00001)        
    else:
      self.freq_inc(-0.00001)
    
  #---------------------------------------------------------------------
  def enter_1hz(self, widget, x):
    l = self.l_1hz.get_label()
    l = l.replace('16', '20')
    self.l_1hz.set_label(l)
  
  #---------------------------------------------------------------------
  def leave_1hz(self, widget, x):
    l = self.l_1hz.get_label()
    l = l.replace('20', '16')
    l = self.l_1hz.set_label(l)
    
  #---------------------------------------------------------------------
  def scroll_1hz(self, widget, e):
    if e.direction == gtk.gdk.SCROLL_UP:
      self.freq_inc(0.000001)
    else:
      self.freq_inc(-0.000001)
  
  #---------------------------------------------------------------------
  #---------------------------------------------------------------------
  # Send the freq inc event
  def freq_inc(self, data):
    self.es.publish('hw_events', ('freq_inc', data))
            
  #=====================================================================
  # Events for the filter and mode buttons
  #---------------------------------------------------------------------  
  def filter_6k(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_filter', '6K0'))

  #---------------------------------------------------------------------  
  def filter_2k4(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_filter', '2K4'))

  #---------------------------------------------------------------------  
  def filter_2k1(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_filter', '2K1'))
  
  #---------------------------------------------------------------------  
  def filter_1k(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_filter', '1K0'))
  
  #---------------------------------------------------------------------  
  def filter_500(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_filter', '500'))
  
  #---------------------------------------------------------------------  
  def filter_250(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_filter', '250'))
  
  #---------------------------------------------------------------------  
  def filter_100(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_filter', '100'))
    
  #---------------------------------------------------------------------  
  def mode_lsb(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_mode', 'LSB'))
  
  #---------------------------------------------------------------------  
  def mode_usb(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_mode', 'USB'))
  
  #---------------------------------------------------------------------  
  def mode_cwl(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_mode', 'CW-L'))
  
  #---------------------------------------------------------------------  
  def mode_cwu(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_mode', 'CW-U'))
  
  #---------------------------------------------------------------------  
  def mode_am(self, widget):
    if not self.ignore:
      if widget.get_active():
        self.es.publish('dsp_events', ('set_mode', 'AM'))
          
  #=====================================================================
  # Event reception
  #--------------------------------------------------------------------- 
  # Something on the AGC changed
  def agc_hints(self, data):
    if data[0] == 'state':
      self.state['agc_state'] = data[1]
      self.ignore = True
      if data[1] == True:
        self.bagc.set_active(True)
        self.bagc.set_label('AGC ON')
      else:
        self.bagc.set_active(False)
        self.bagc.set_label('AGC OFF')
      self.ignore = False
    elif data[0] == 'rate':
      self.state['agc_rate'] = data[1]
      self.set_agc_hints(data[1])
  
  #---------------------------------------------------------------------
  # Change of frequency
  def freq_change(self, data):
    self.state['frequency'] = data
    # Convert frequency to string parts
    smhz, skhz, shz = utils.FreqToString(data)
    # Set the whole frequency
    self.l_100mhz.set_label('<span font_desc=\"Times 24\" foreground=\"red\">'+smhz[0]+'</span>')
    self.l_10mhz.set_label('<span font_desc=\"Times 24\" foreground=\"red\">'+smhz[1]+'</span>')
    self.l_1mhz.set_label('<span font_desc=\"Times 24\" foreground=\"red\">'+smhz[2]+'</span>')    
    self.l_100khz.set_label('<span font_desc=\"Times 20\" foreground=\"red\">'+skhz[0]+'</span>')
    self.l_10khz.set_label('<span font_desc=\"Times 20\" foreground=\"red\">'+skhz[1]+'</span>')
    self.l_1khz.set_label('<span font_desc=\"Times 20\" foreground=\"red\">'+skhz[2]+'</span>')
    self.l_100hz.set_label('<span font_desc=\"Times 16\" foreground=\"red\">'+shz[0]+'</span>')
    self.l_10hz.set_label('<span font_desc=\"Times 16\" foreground=\"red\">'+shz[1]+'</span>')
    self.l_1hz.set_label('<span font_desc=\"Times 16\" foreground=\"red\">'+shz[2]+'</span>')
  
  #--------------------------------------------------------------------- 
  # Sent at start of day to initialise the frequency
  def init_freq(self, data):
    self.freq_change(data)
    
  #--------------------------------------------------------------------- 
  # Mode changed
  def mode_hints(self, data):
    self.state['mode'] = data
    self.set_mode_hints(data)
  
  #---------------------------------------------------------------------  
  # Filter changed
  def filter_hints(self, data):
    self.state['filter'] = data
    self.set_filter_hints(data)
    
  #=====================================================================
  # Entry point
  def main(self):
    # All PyGTK applications must have a gtk.main(). Control ends here
    # and waits for an event to occur (like a key press or mouse event)
    gtk.main()
    # Return here when the UI is closed

  #=====================================================================
  # Private
  
  #---------------------------------------------------------------------
  # These three hints methods are called from the message receivers. The
  # purpose is to set the correct state on the toggle buttons. The UI uses
  # as set of toggle buttons for modes etc but we need to release all
  # the buttons and select the correct one. Of course you don't see this in
  # the UI because it happens too fast. It just looks like they are in a group.
  # Note we set to ignore events during this process.
  #---------------------------------------------------------------------  
  def set_mode_hints(self, mode):
    self.ignore = True
    dict = {'LSB':0,'USB':1,'CW-L':2,'CW-U':3,'AM':4,'FM-N':5}
    widgets = [self.blsb, self.busb, self.bcwl, self.bcwu, self.bam]
    for n in range(len(widgets)):
      if n == dict[mode]:
        widgets[n].set_active(True)
      else:
        widgets[n].set_active(False)
    self.ignore = False
  
  #---------------------------------------------------------------------  
  def set_filter_hints(self, filter):
    self.ignore = True
    dict = {'6K0':0,'2K4':1,'2K1':2,'1K0':3,'500':4,'250':5, '100':6}
    widgets = [self.b6k, self.b2k4, self.b2k1, self.b1k, self.b500, self.b250, self.b100]
    for n in range(len(widgets)):
      if n == dict[filter]:
        widgets[n].set_active(True)
      else:
        widgets[n].set_active(False)
    self.ignore = False
    
  #---------------------------------------------------------------------  
  def set_agc_hints(self, agc):
    self.ignore = True
    rates = ['AGC_FAST', 'AGC_MED', 'AGC_SLOW']
    widgets = [self.bfast, self.bmedium, self.bslow]
    for n in range(len(rates)):
      widgets[n].set_active(False)
      if agc == rates[n]:
        widgets[n].set_active(True)
    self.ignore = False